package rpcagent

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
	"time"

	"ac"
	log "ac-common-go/glog"
)

const (
	contentType = "Content-Type"
	textJSON    = "application/json"
)

type jsonError struct {
	Code int64  `json:"errorCode"`
	Err  string `json:"error"`
}

type RRLoadBalancer struct {
	indexes uint64
	rwMutex sync.RWMutex
}

func NewRRLoadBalancer() *RRLoadBalancer {
	return &RRLoadBalancer{indexes: 0}
}

func (lb *RRLoadBalancer) GetEndpoint(endpoints []string) (endpoint string) {
	lb.rwMutex.Lock()
	defer lb.rwMutex.Unlock()

	index := lb.indexes
	pos := index % uint64(len(endpoints))
	endpoint = endpoints[pos]
	lb.indexes = index + uint64(1)
	return endpoint
}

type Proxy struct {
	Verbose      bool
	Addrs        []string
	Port         string
	client       *http.Client
	loadBalancer *RRLoadBalancer
	retryNum     int
}

func NewHTTPClient(maxConnsPerHost, respHeaderTimeout, idleTimeout int) *http.Client {
	HTTPClient := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost:   maxConnsPerHost,
			IdleConnTimeout:       time.Duration(idleTimeout) * time.Second,
			ResponseHeaderTimeout: time.Duration(respHeaderTimeout) * time.Second,
		},
		Timeout: time.Duration(respHeaderTimeout) * time.Second,
	}
	return HTTPClient
}

func NewProxy(addrs, port string, maxConnsPerHost, respHeaderTimeout, idleTimeout, retryNum int, verbose bool) *Proxy {
	return new(Proxy).Init(addrs, port, maxConnsPerHost, respHeaderTimeout, idleTimeout, retryNum, verbose)
}

func (p *Proxy) Init(addrs, port string, maxConnsPerHost, respHeaderTimeout, idleTimeout, retryNum int, verbose bool) *Proxy {
	ipArray := strings.Split(addrs, "*")
	p.Addrs = ipArray
	p.Port = port
	p.Verbose = verbose
	p.client = NewHTTPClient(maxConnsPerHost, respHeaderTimeout, idleTimeout)
	p.loadBalancer = NewRRLoadBalancer()
	p.retryNum = retryNum
	return p
}

func (c *Proxy) DirectCall(ctx *ac.ZContext, method, httpType, params string, args, reply interface{}) error {
	//ipArray := strings.Split(c.Addrs, "*")

	// 编码args
	var body io.Reader
	//默认重试一次
	for i := 0; i < c.retryNum; i++ {
		serviceIP := c.loadBalancer.GetEndpoint(c.Addrs)
		// 构造请求
		var url string
		if httpType == "GET" {
			url = "http://" + serviceIP + ":" + c.Port + "/" + method + "?" + params
		} else {
			url = "http://" + serviceIP + ":" + c.Port + "/" + method

			if args != nil {
				payload, err := json.Marshal(args)
				if err != nil {
					log.ErrorfT(ctx, "josn marshal: args[%+v], err[%v]", args, err)
					return err
				}
				body = bytes.NewReader(payload)
			}

		}

		req, err := http.NewRequest(httpType, url, body)
		if err != nil {
			log.ErrorfT(ctx, "new http request: url[%s], err[%v]", url, err)
			return err
		}

		req.Header.Set(contentType, textJSON)
		// 打印请求日志
		if c.Verbose {
			dump, err := httputil.DumpRequestOut(req, true)
			if err != nil {
				log.InfofT(ctx, "dump request out: err[%v]", err)
			} else {
				log.InfofT(ctx, "Request:\n%s", dump)
			}
		}

		// 发送请求
		resp, err := c.client.Do(req)
		if err != nil {
			log.ErrorfT(ctx, "http client do request: url[%s], err[%v]", url, err)
			continue
		}
		defer resp.Body.Close()

		// 打印响应日志
		if c.Verbose {
			dump, err := httputil.DumpResponse(resp, true)
			if err != nil {
				log.InfofT(ctx, "dump response: err[%v]", err)
			} else {
				log.InfofT(ctx, "Response:\n%s", dump)
			}
		}

		// 响应处理
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.ErrorfT(ctx, "read response body: url[%s], err[%v]", url, err)
			return err
		}

		if reply != nil && resp.StatusCode == 200 {
			if err = json.Unmarshal(data, reply); err != nil {
				log.ErrorfT(ctx, "json unmarshal: data[%s], value[%+v], err[%v]", data, reply, err)
				return err
			}
		} else {
			log.ErrorfT(ctx, "resp error resp.StatusCode %d", resp.StatusCode)
		}
	}
	return nil
}
